system design · inside the pod

What's really
riding inside your pod?

Your app was never travelling alone. A sidecar container rides along in the same pod — and when every pod gets one, the whole cluster grows a nervous system called a service mesh.

POD App Sidecar same localhost · same lifecycle · same volumes
a new dimension

There are two completely different traffic problems

One is "how does a request from outside get to one pod?" — that's North-South traffic, and load balancers/gateways solve it. The other is "how do services inside the cluster talk to each other?" — that's East-West traffic, and it's a different shape of problem entirely.

North-South · outside → in

Client │ ▼ DNS → WAF → LB → Gateway │ ▼ K8s Service → Pod

East-West · inside ↔ inside

Order PodPayment PodInventory PodNotification Pod

A single client request can trigger 10+ East-West calls before a response goes back. This is the traffic a service mesh exists to manage — and the sidecar pattern is how it gets in position to do it.

first, the building block

What's actually inside a Pod?

A Pod is never just "your app." It's one or more containers that are always scheduled together, on the same machine, sharing the same network identity.

POD (single IP: 10.4.2.17) ┌───────────────────────────────────┐ │ App Container │ │ listens on localhost:8080 │ │ │ │ ╌╌╌╌╌╌╌ same network namespace ╌╌╌╌╌╌╌ │ │ │ │ Sidecar Container │ │ listens on localhost:9090 │ │ │ │ same volumes · same lifecycle · │ │ created together, killed together │ └───────────────────────────────────┘

Because they share "localhost," the sidecar can watch or intercept everything the app does — without the app calling any external service or even knowing the sidecar exists.

the logic behind the pattern

Why bolt on a sidecar instead of coding it into the app?

Think of a motorcycle sidecar: it travels everywhere the bike goes, starts and stops with it — but it isn't the engine, and it doesn't steer. It just carries something the rider doesn't want strapped to their own back. That's the whole idea. Monitoring, logging, and network policy are not your app's job — they're concerns that repeat identically across every service, regardless of whether that service is written in Go, Java, or Python. Push them into a sidecar once, and every app inherits them for free, upgraded on its own schedule, never touching app code.
tap to expand

Four things people actually put in a sidecar

concrete walkthrough

A monitoring agent sidecar, step by step

App Container Sidecar (agent) exposes metrics at scrapes it locally localhost:8080/metrics ───────▶ every 15 seconds │ │ batches + ships out ▼ Monitoring backend (Prometheus · Datadog · Grafana)

Nothing here ever leaves the pod insecurely — the app never needs an outbound network call, an API key for the monitoring vendor, or any code changes when you switch monitoring tools. Swap Datadog for Prometheus by swapping the sidecar, not the app.

now scale the same idea to networking

That's a service mesh

Same sidecar pattern — but instead of just watching traffic, this sidecar (usually Envoy) sits directly inside the traffic path and intercepts everything in and out of the pod.

Without a mesh

Order App │ raw HTTP call ▼ Payment App retries, timeouts, TLS, tracing — all hand-coded inside both apps

With a mesh

Order App (calls "payment-service" normally) │ localhost ▼ Order's sidecar proxy │ mTLS + retries ▼ Payment's sidecar proxy │ localhost ▼ Payment App

The thought-provoking part: the app's own code is identical in both diagrams. It still just calls payment-service:8080. Traffic rules injected at pod startup silently redirect that call through the sidecar first. The app is unaware the mesh even exists.

who's actually in charge

Control plane vs data plane

Every sidecar proxy in the mesh is dumb on its own — it just enforces rules. One central brain writes those rules and pushes them to every proxy at once.

Control Plane — Istio / Linkerd
pushes routing rules, retry policy, and mTLS certificates to every proxy
▼  configures every sidecar below, continuously  ▼
Pod · Order Service
App
Envoy proxy
mTLS
traffic
Pod · Payment Service
App
Envoy proxy
mTLS
traffic
Pod · Inventory Service
App
Envoy proxy

Dashed lines = configuration (control plane → every proxy). Solid ⇄ = actual request traffic (proxy → proxy, data plane). The apps never talk to each other directly.

what it actually buys you

Capabilities that move out of your app

CapabilityWithout a meshWith a mesh
Retries & timeoutsHand-coded per app, per languageDeclarative config, same for every service
mTLS between servicesEach app manages its own TLS certsSidecar handles it automatically
Circuit breakingCustom libraries, inconsistentUniform, proxy-enforced
Per-hop observabilityManual instrumentation, easy to miss a serviceAutomatic — every hop looks the same
Canary between servicesApp-level feature flagsMesh routing rules, zero app changes
the nuance worth remembering

Not every sidecar is the same kind of sidecar

Passive sidecar

  • Watches or scrapes — never sits in the request path
  • Example: monitoring agent, log shipper
  • If it dies, your app's traffic is unaffected
  • Adds visibility, adds zero request latency

In-path sidecar

  • Every single request physically flows through it
  • Example: service mesh proxy (Envoy, linkerd-proxy)
  • If it dies, that pod loses network access
  • Adds control (retries, mTLS) — at the cost of one extra hop
A sidecar doesn't change what your app does.
It changes what your app no longer has to do.
a mechanism most people never see

You wrote a pod with one container. Where did the second one come from?

Nobody edits YAML to add a sidecar by hand at scale. Kubernetes intercepts your request before it's even saved and rewrites it.

kubectl apply -f order-pod.yaml (you wrote 1 container) │ ▼ Kubernetes API Server │ │ "before I save this, let me check for a │ mutating webhook registered on Pods..." ▼ Mutating Admission Webhook (istio-sidecar-injector) │ │ rewrites the spec in-flight: │ + Envoy sidecar container │ + init-container (installs iptables rules) ▼ Pod actually scheduled — with 2 containers
worth sitting with You never approved that second container. The API server did — silently, on every single pod, cluster-wide. That's what makes the mesh "transparent." It's also exactly why a broken sidecar injector can quietly stop your entire cluster from scheduling anything.
use case 1 · progressive delivery

Shipping a risky release without betting the whole system on it

The mesh can split traffic between two versions of the same service by percentage — no app code change, no second deploy pipeline, no feature flag library.

order-service v1 · 90%
v2 · 10%
stable, battle-testednew release, watched closely
route order-service: - destination: v1 weight: 90 - destination: v2 weight: 10

Watch v2's error rate and latency for an hour. Error rate flat? Shift to 50/50, then 100. Error rate spikes? Shift back to 100/0 in one config change — no redeploy, no rollback pipeline, no user-facing downtime.

use case 2 · the thought experiment

What happens when one dependency quietly gets slow?

Not down. Just slow. This is the failure mode that takes down entire platforms — and it's almost never the slow service itself that causes the outage.

follow the chain Inventory Service starts responding in 4s instead of 40ms. Order Service times out at 2s and retries 3 times — "just to be safe." Every one of those retries is a brand new request hitting an already-struggling Inventory Service. Multiply that by every concurrent Order request, and a single slow dependency turns into 10x–100x amplified load hitting the one service that can least afford it. Inventory doesn't just stay slow — it falls over completely, and takes Order Service down with it through exhausted connection pools.

A mesh sidecar breaks this chain the same way an electrical circuit breaker does — by refusing to send more current once it detects a fault:

CLOSED
requests flow normally
OPEN
failures cross threshold —
proxy fails fast, no retry storm
HALF-OPEN
let a trickle through
to test recovery
CLOSED
back to normal

Notice what didn't change: Order Service's code. It still calls Inventory the same way. The proxy made the "stop hammering a dying service" decision on its behalf, in milliseconds, without a human on call.

use case 3 · "why was this request slow?"

Tracing a request across five services nobody instrumented by hand

trace-id: 8f3a... (assigned once, at the Gateway) Gateway [██] 5ms Order [████] 12ms Payment [████████] 20ms ← bottleneck Inventory [███] 6ms Notification [██] 3ms (fire-and-forget) └──────────────────────────────────┘ total: 46ms
worth sitting with Nobody wrote timing code in any of these five services. Each sidecar simply read the trace-id header its neighbor forwarded, stamped its own start/end time against it, and shipped that span to a collector (Jaeger/Zipkin). The moment your organization gets a service mesh, every future service is traceable by default — tracing becomes infrastructure, not a feature teams remember to build.
use case 4 · the mental model shift

Zero trust: stop assuming your own network is safe

The old model: "we're inside the VPC, traffic between our own services doesn't need encryption." The mesh model: assume any hop could be observed — encrypt everything, service-to-service, automatically.

Order Service pod Payment Service pod ┌─────────────────┐ ┌─────────────────┐ │ App │ │ App │ │ Envoy 🔒 ────────┼──── mTLS ────┼──── 🔒 Envoy │ └─────────────────┘ encrypted └─────────────────┘ + mutually authenticated (cert per workload, rotated automatically)

Neither app wrote a line of TLS code. Neither app even has the private key — the sidecar does, and the control plane rotates it before it ever expires. A compromised node inside your own VPC still can't read or spoof traffic between two meshed services.

the honest part nobody puts in the pitch deck

Every sidecar is also a tax

×2
containers per pod
+50–100MB
memory, per sidecar
+1 hop
latency, every single call
500+
extra Envoy processes at 500-pod scale

At 500 pods, that's not a rounding error — it's meaningfully extra compute spend and one more process per pod that can crash, lag, or misconfigure. This is exactly why Istio's newer "ambient mesh" mode exists: instead of one proxy per pod, it runs one lightweight proxy per node (called a ztunnel), shared by every pod on that machine — trading a little isolation for a lot less overhead.

Sidecar mode

  • One Envoy per pod — maximum isolation
  • Injected automatically at pod creation
  • Cost scales linearly with pod count

Ambient mode (no sidecar)

  • One shared proxy per node, not per pod
  • Lower memory/CPU overhead at scale
  • Newer, still maturing — fewer per-pod guarantees
closing the loop, honestly

When should you actually not reach for a service mesh?

SignalWhat it usually means
Fewer than ~10 services, mostly one teamSkip it — a gateway + good libraries are enough
No dedicated platform/SRE capacity to own itSkip it — an unowned mesh becomes an outage generator
Regulated industry, mandatory mTLS + audit trailsStrong case for it
Frequent cross-service retries/cascading failures todayStrong case for it
Need uniform tracing across 20+ polyglot servicesStrong case for it
A service mesh doesn't remove complexity.
It moves complexity from every app's code into one place you now have to operate.
quick recall

Flip each card — name the question or the one-liner

tap a card to reveal · tap again to hide